package pl.edu.pb.wi.cookbook.service;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import pl.edu.pb.wi.cookbook.entity.Category;
import pl.edu.pb.wi.cookbook.entity.CookbookRecipe;
import pl.edu.pb.wi.cookbook.entity.Recipe;
import pl.edu.pb.wi.cookbook.entity.UserRecipePoints;
import pl.edu.pb.wi.cookbook.util.FullTextSearchUtil;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.Query;

@Service
public class CategoryService implements IFullTextSearch<Category> {
    private static final String CATEGORY_MARKER = "-";
    private static final String ROOT_CATEGORY_NAME = "Korzeń";

    private Objectify oby;

    @Autowired
    public CategoryService(ObjectifyFactory objectifyFactory) {
        this.oby = objectifyFactory.begin();
    }

    /**
     * @param name
     *            nazwa
     * @param description
     *            opis
     * @param parentCategory
     *            jeżeli null to kategoria będzie przydzielona do korzenia
     *            drzewa kategorii
     * @return utworzoną kategorię
     */
    public Category createCategory(String name, String description,
            boolean canAddRecipes, Key<Category> parentCategory) {
        Category newCat = new Category();
        newCat.setName(name);
        newCat.setDescription(description);
        newCat.setCanAddRecipes(canAddRecipes);
        newCat.setParentCategory(parentCategory);

        newCat.updateFts();

        oby.put(newCat);

        return newCat;
    }

    /**
     * @param name
     *            nazwa
     * @param description
     *            opis
     * @param parentCategory
     *            jeżeli null to kategoria będzie przydzielona do korzenia
     *            drzewa kategorii
     * @return utworzoną kategorię
     */
    public Category createCategory(String name, String description,
            boolean canAddRecipes, Long parentCategoryId) {
        Category newCat = new Category();
        newCat.setName(name);
        newCat.setDescription(description);
        newCat.setCanAddRecipes(canAddRecipes);
        newCat.setParentCategory(new Key<Category>(Category.class,
                parentCategoryId));

        newCat.updateFts();

        oby.put(newCat);

        return newCat;
    }

    public Category getCategory(Long id) {
        return oby.get(new Key<Category>(Category.class, id));
    }

    public Category getCategory(Key<Category> catKey) {
        return oby.get(catKey);
    }

    public boolean isCategoryInDatabase(Long id) {
        return oby.get(new Key<Category>(Category.class, id)) == null ? false
                : true;
    }

    public boolean isCategoryActive(Category current, Category menuCat) {
        if (current != null && menuCat != null) {
            while (current.getParentCategory() != null) {
                current = oby.get(current.getParentCategory());
            }

            return current.getId().equals(menuCat.getId());
        } else
            return false;
    }

    public List<Category> getAllCategories() {
        return oby.query(Category.class).list();
    }

    public Category getFirstCategory() {
        return oby.query(Category.class).filter("parentCategory", null).get();
    }

    public Integer getSubcategoryCount(Long id) {
        List<Category> catList = oby
                .query(Category.class)
                .filter("parentCategory", new Key<Category>(Category.class, id))
                .list();

        return catList != null ? catList.size() : null;
    }

    public Integer getCategoryRecipeCount(Long id) {
        List<Recipe> recList = oby.query(Recipe.class)
                .filter("category", new Key<Category>(Category.class, id))
                .list();

        return recList != null ? recList.size() : null;
    }

    public List<Category> getMainCategories() {
        return oby.query(Category.class).filter("parentCategory", null).list();
    }

    public List<Key<Category>> getAllCategoriesKeys() {
        return oby.query(Category.class).listKeys();
    }

    public List<Category> getCategoryChildren(Category cat) {
        return oby
                .query(Category.class)
                .filter("parentCategory",
                        new Key<Category>(Category.class, cat.getId())).list();
    }

    public List<Category> getCategoryChildren(Key<Category> catKey) {
        return oby.query(Category.class).filter("parentCategory", catKey)
                .list();
    }

    public List<Key<Category>> getCategoryChildrenKeys(Category cat) {
        return oby
                .query(Category.class)
                .filter("parentCategory",
                        new Key<Category>(Category.class, cat.getId()))
                .listKeys();
    }

    public List<Key<Category>> getCategoryChildrenKeys(Key<Category> catKey) {
        return oby.query(Category.class).filter("parentCategory", catKey)
                .listKeys();
    }

    public void remove(Category cat) {
        Key<Category> root = new Key<Category>(Category.class, cat.getId());

        List<Key<Category>> childrenList = new LinkedList<Key<Category>>();
        getAllChildren(root, childrenList);
        childrenList.add(root);

        List<Key<Recipe>> recipeList = new LinkedList<Key<Recipe>>();
        List<Key<Recipe>> categoryRecipes = null;
        for (Key<Category> key : childrenList) {
            categoryRecipes = oby.query(Recipe.class).filter("category", key)
                    .listKeys();

            recipeList.addAll(categoryRecipes);
        }

        List<Key<CookbookRecipe>> cookbookRecipe = new LinkedList<Key<CookbookRecipe>>();
        List<Key<CookbookRecipe>> cookbookRecipes = null;
        for (Key<Recipe> key : recipeList) {
            cookbookRecipes = oby.query(CookbookRecipe.class)
                    .filter("recipe", key).listKeys();

            cookbookRecipe.addAll(cookbookRecipes);
        }

        List<Key<UserRecipePoints>> userRecipePoint = new LinkedList<Key<UserRecipePoints>>();
        List<Key<UserRecipePoints>> userRecipePoints = null;
        for (Key<Recipe> key : recipeList) {
            userRecipePoints = oby.query(UserRecipePoints.class)
                    .filter("recipe", key).listKeys();

            userRecipePoint.addAll(userRecipePoints);
        }

        oby.delete(userRecipePoint);
        oby.delete(cookbookRecipe);
        oby.delete(recipeList);
        oby.delete(childrenList);
    }

    public Key<Category> updateCategory(Category category) {
        category.updateFts();

        return oby.put(category);
    }

    private void getAllChildren(Key<Category> parentCategory,
            List<Key<Category>> catList) {
        List<Key<Category>> categories = oby.query(Category.class)
                .filter("parentCategory", parentCategory).listKeys();

        for (Key<Category> key : categories) {
            catList.add(key);
            getAllChildren(key, catList);
        }
    }

    public Map<String, Long> getAllCategoriesButOneAsMap(Long id) {
        Map<String, Long> map = new LinkedHashMap<String, Long>();
        map.put(ROOT_CATEGORY_NAME, Long.valueOf(-1));

        makeCategory(null, CATEGORY_MARKER, map, id);

        return map;
    }

    public Map<String, Long> getAllCategoriesAsMap() {
        Map<String, Long> map = new LinkedHashMap<String, Long>();
        map.put(ROOT_CATEGORY_NAME, Long.valueOf(-1));

        makeCategory(null, CATEGORY_MARKER, map);

        return map;
    }

    public Map<String, Long> getAllButRootCategoriesAsMap() {
        Map<String, Long> map = new LinkedHashMap<String, Long>();

        makeCategory(null, CATEGORY_MARKER, map);

        return map;
    }

    private void makeCategory(Key<Category> parentCategory, String separator,
            Map<String, Long> map, Long id) {
        List<Category> categories = oby.query(Category.class)
                .filter("parentCategory", parentCategory).list();

        for (Category cat : categories) {
            if (!cat.getId().equals(id)) {
                map.put(String.format("%s %s", separator, cat.getName()),
                        cat.getId());
                makeCategory(new Key<Category>(Category.class, cat.getId()),
                        String.format("%s-", separator), map, id);
            }
        }
    }

    private void makeCategory(Key<Category> parentCategory, String separator,
            Map<String, Long> map) {
        List<Category> categories = oby.query(Category.class)
                .filter("parentCategory", parentCategory).list();

        for (Category cat : categories) {
            map.put(String.format("%s %s", separator, cat.getName()),
                    cat.getId());
            makeCategory(new Key<Category>(Category.class, cat.getId()),
                    String.format("%s-", separator), map);
        }
    }

    @Override
    public List<Category> search(String queryString) {
        Query<Category> query = oby.query(Category.class);

        Set<String> queryTokens = FullTextSearchUtil
                .getTokensForIndexingOrQuery(queryString,
                        FullTextSearchUtil.MAXIMUM_NUMBER_OF_WORDS_TO_SEARCH);
        List<String> queryTokensList = new ArrayList<String>(queryTokens);

        for (String token : queryTokensList) {
            query.filter("fts", token);
        }

        List<Category> result = new ArrayList<Category>();
        for (Category category : query) {
            result.add(category);
        }

        return result;
    }
}
